
/*
 * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI 
 * for visualizing and manipulating spatial features with geometry and attributes.
 *
 * JUMP is Copyright (C) 2003 Vivid Solutions
 *
 * This program implements extensions to JUMP and is
 * Copyright (C) 2004 Integrated Systems Analysts, Inc.
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 * For more information, contact:
 *
 * Integrated Systems Analysts, Inc.
 * 630C Anchors St., Suite 101
 * Fort Walton Beach, Florida
 * USA
 *
 * (850)862-7321
 */

package org.openjump.core.ui.plugin.edittoolbox.cursortools;

import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.util.ArrayList;

import javax.swing.Icon;
import javax.swing.ImageIcon;

import org.openjump.core.geomutils.Circle;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.operation.valid.IsValidOp;
import com.vividsolutions.jump.I18N;
import com.vividsolutions.jump.workbench.ui.EditTransaction;
import com.vividsolutions.jump.workbench.ui.LayerNamePanelProxy;
import com.vividsolutions.jump.workbench.ui.cursortool.CursorTool;
import com.vividsolutions.jump.workbench.ui.cursortool.editing.FeatureDrawingUtil;

public class DrawConstrainedCircleTool extends ConstrainedMultiClickTool {
    private FeatureDrawingUtil featureDrawingUtil;
    final static String drawConstrainedCircle =I18N.get("org.openjump.core.ui.plugin.edittoolbox.cursortools.DrawConstrainedCircleTool.Draw-Constrained-Circle");
    final static String theCircleMustHaveAtLeast2Points=I18N.get("org.openjump.core.ui.plugin.edittoolbox.cursortools.DrawConstrainedCircleTool.The-circle-must-have-at-least-2-points");
    
    private DrawConstrainedCircleTool(FeatureDrawingUtil featureDrawingUtil) {
        drawClosed = true;
        this.featureDrawingUtil = featureDrawingUtil;
    }

    public static CursorTool create(LayerNamePanelProxy layerNamePanelProxy) {
        FeatureDrawingUtil featureDrawingUtil = new FeatureDrawingUtil(layerNamePanelProxy);

        return featureDrawingUtil.prepare(new DrawConstrainedCircleTool(
                featureDrawingUtil), true);
    }

    public String getName() {
        return drawConstrainedCircle;
    }

    public Icon getIcon() {
        return new ImageIcon(getClass().getResource("DrawCircleConstrained.gif"));
    }

    protected void gestureFinished() throws Exception {
        reportNothingToUndoYet();

        if (!checkCircle()) {
            return;
        }

        execute(featureDrawingUtil.createAddCommand(getCircle(),
                isRollingBackInvalidEdits(), getPanel(), this));
    }

    protected Polygon getCircle() throws NoninvertibleTransformException
    {
        // Subject 1.04: How do I generate a circle through three points?
        //
        //    Let the three given points be a, b, c.  Use _0 and _1 to represent
        //    x and y coordinates. The coordinates of the center p=(p_0,p_1) of
        //    the circle determined by a, b, and c are:
        //
        //        A = b_0 - a_0;
        //        B = b_1 - a_1;
        //        C = c_0 - a_0;
        //        D = c_1 - a_1;
        //
        //        E = A*(a_0 + b_0) + B*(a_1 + b_1);
        //        F = C*(a_0 + c_0) + D*(a_1 + c_1);
        //
        //        G = 2.0*(A*(c_1 - b_1)-B*(c_0 - b_0));
        //
        //        p_0 = (D*E - B*F) / G;
        //        p_1 = (A*F - C*E) / G;
        //
        //    If G is zero then the three points are collinear and no finite-radius
        //    circle through them exists.  Otherwise, the radius of the circle is:
        //
        //            r^2 = (a_0 - p_0)^2 + (a_1 - p_1)^2
        //
        //    Reference:
        //    [O' Rourke (C)] p. 201. Simplified by Jim Ward.
        
        double angle = 360.0;
        ArrayList points = new ArrayList(getCoordinates());
        
        if (getCoordinates().size() == 2)
        {
            Circle circle = new Circle((Coordinate) points.get(0), ((Coordinate) points.get(0)).distance((Coordinate) points.get(1)));
            return circle.getPoly();
        }
        else
        {
            Coordinate a = (Coordinate) points.get(0);
            Coordinate b = (Coordinate) points.get(1);
            Coordinate c = (Coordinate) points.get(2);
            return getCircle3points(a, b, c);
        }
    }
    
    private Polygon getCircle3points(Coordinate a, Coordinate b, Coordinate c){
        
        double A = b.x - a.x;
        double B = b.y - a.y;
        double C = c.x - a.x;
        double D = c.y - a.y;
        double E = A * (a.x + b.x) + B * (a.y + b.y);
        double F = C * (a.x + c.x) + D * (a.y + c.y);
        double G = 2.0 * (A * (c.y - b.y ) - B * (c.x - b.x));
        if (G != 0.0)
        {
            double px = (D * E - B * F) / G;
            double py = (A * F - C * E) / G;
            Coordinate center = new Coordinate(px, py);
            double radius = Math.sqrt((a.x - px) * (a.x - px) + (a.y - py) * (a.y - py)); 
            Circle circle = new Circle(center, radius);
            return circle.getPoly();
        }
        else //points are colinear; use second for center; third for the radius
        {
            Circle circle = new Circle(b, (b).distance(c));
            return circle.getPoly();
        }
    }

    protected boolean checkCircle() throws NoninvertibleTransformException {
        if (getCoordinates().size() < 2) {
            getPanel().getContext().warnUser(theCircleMustHaveAtLeast2Points);

            return false;
        }

        IsValidOp isValidOp = new IsValidOp(getCircle());

        if (!isValidOp.isValid()) {
            getPanel().getContext().warnUser(isValidOp.getValidationError()
                                                      .getMessage());

            if (getWorkbench().getBlackboard().get(EditTransaction.ROLLING_BACK_INVALID_EDITS_KEY, false)) {
                return false;
            }
        }

        return true;
    }
    
    /*
     * (non-Javadoc)
     * @see com.vividsolutions.jump.workbench.ui.cursortool.AbstractCursorTool#getShape()
     */
    protected Shape getShape() throws NoninvertibleTransformException {
		if (coordinates.size() > 1)
        {

			GeneralPath shape = new GeneralPath();
			
            Coordinate a = (Coordinate) coordinates.get(0);
            Coordinate b = (Coordinate) coordinates.get(1);
            Coordinate c = tentativeCoordinate;
            
			Polygon polygon = getCircle3points(a, b, c);
			Coordinate[] polygonCoordinates = polygon.getCoordinates();
			
			Coordinate firstCoordinate = (Coordinate) polygonCoordinates[0];
            Point2D firstPoint = getPanel().getViewport().toViewPoint(firstCoordinate);
            shape.moveTo((float) firstPoint.getX(), (float) firstPoint.getY());
			
			for (int i = 1, n = polygonCoordinates.length; i < n; i++)
			{
				Point2D nextPoint = getPanel().getViewport().toViewPoint( polygonCoordinates[i]);
				shape.lineTo((int) nextPoint.getX(), (int) nextPoint.getY());
			}
			return shape;
        }
		return super.getShape();
	}
   
}